#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <memory>
#include "thread.hpp"

// 无论是多线程读还是写，用的sock都是一个，sock代表就是文件
// 因为UDP是全双工的,可以同时进行收发而不受干扰

//定义全局的服务器ip和端口
uint16_t serverport = 0; 
std::string serverip;

static void usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << " serverIp serverPort\n"
              << std::endl;
}

//线程发送数据的回调方法
static void* udpSend(void *args)
{
    //拿到sock和name
    int sock = *(int*)((ThreadData*)args)->args_;
    std::string name = ((ThreadData*)args)->name_;

    std::string message;                    //getline的缓冲区
    struct sockaddr_in server;              //sockaddr_in的结构体，表示服务端的信息
    memset(&server, 0, sizeof(server));     //清空结构体数据，完成初始化
    server.sin_family = AF_INET;            //表示网络通信
    server.sin_port = htons(serverport);    //将端口号主机序列转换为网络序列
    server.sin_addr.s_addr = inet_addr(serverip.c_str()); //转换IP地址风格

    //客户端开始给服务器发消息
    while (true)
    {
        std::cerr << "请输入你的信息# ";     //标准错误 2打印
        std::getline(std::cin, message);    //获取消息
        if (message == "quit")              //如果是quit，客户端退出
            break;
        // 当client首次发送消息给服务器的时候，OS会自动给client bind他的IP和PORT
        // sendto发送消息
        sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof server);
    }
    return nullptr;
}


//线程读取数据的回调方法
static void* udpRecv(void *args)
{
    int sock = *(int *)((ThreadData *)args)->args_;
    std::string name = ((ThreadData *)args)->name_;
    char buffer[1024];//定义接收数据的缓冲区
    while (true)
    {
        memset(buffer, 0, sizeof(buffer));  //初始化缓冲区
        struct sockaddr_in temp;        //一般不用，占位符
        socklen_t len = sizeof(temp);   //一般不用，占位符
        //从sock读取数据
        ssize_t s = recvfrom(sock, buffer, sizeof buffer, 0, (struct sockaddr *)&temp, &len);
        //recvfrom返回接收到的字节数
        if (s > 0)
        { 
            buffer[s] = 0;//先当字符串
            //输出数据
            std::cout  << buffer << std::endl;
        }
    }
}

// ./udp_client 127.0.0.1 8080
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        //参数错误，打印客户端的使用手册
        usage(argv[0]);
        exit(1);
    }
    //创建套接字
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    //创建套接字失败提示
    if (sock < 0)
    {
        std::cerr << "socket error" << std::endl;
        exit(2);
    }
    //获取服务器的ip和端口号
    serverport = atoi(argv[2]);
    serverip = argv[1];

    // client一般不需要显示的bind指定port，而是让OS自动随机选择
    //当client首次发送（第一次sendto的时候）消息给服务器的时候，OS会自动给client bind他的IP和PORT

    std::unique_ptr<Thread> sender(new Thread(1, udpSend, (void*)&sock));//发送消息的线程
    std::unique_ptr<Thread> recver(new Thread(2, udpRecv, (void*)&sock));//接收消息的线程

    //线程启动
    sender->start();
    recver->start();

    //线程等待
    sender->join();
    recver->join();

    close(sock); //关闭套接字


    return 0;
}

//127.0.0.0.1 是本地环回，表示clent和server发送数据只在本地协议栈进行数据流动，不会把数据发送到网络中
//通常用来本地网络服务器的测试。